package common.db.dao;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javassist.ClassClassPath;
import javassist.ClassPool;

import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import core.config.Factory;

/**
 * DAO共通クラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class DaoUtil {

	/**
	 * コンストラクタ
	 *
	 */
	private DaoUtil() {
		throw new AssertionError();
	}

	/**
	 * Id取得メソッド取得
	 * @param <T> ジェネリックス
	 * @param cls エンティティクラス
	 * @return ID取得メソッド
	 */
	public static <T> Method getIdMethod(final Class<T> cls) {
		for (final Method mt : cls.getMethods()) {
			if (Factory.isGetter(mt) && isId(mt)) {
				return mt;
			}
		}
		return null;
	}

	/**
	 * シーケンス名取得
	 * @param <T> ジェネリクス
	 * @param cls エンティティクラス
	 * @return シーケンス名
	 */
	public static <T> String getSequenceName(final Class<T> cls) {
		Method m = getIdMethod(cls);
		if (m != null) {
			GeneratedValue gv = m.getAnnotation(GeneratedValue.class);
			if (gv != null && gv.generator() != null) {
				SequenceGenerator sg = m.getAnnotation(SequenceGenerator.class);
				if (sg != null && gv.generator().equals(sg.name())) {
					return sg.sequenceName();
				}
			}
		}
		return "";
	}

	/**
	 * 複合キー確認
	 * @param cls エンティティクラス
	 * @return 複合キークラスの場合 true を返す。
	 */
	public static boolean isEmbeddedId(final Class<?> cls) {
		Method m = getIdMethod(cls);
		return m != null && m.getAnnotation(EmbeddedId.class) != null;
	}

	/**
	 * テーブル名取得
	 * @param cls モデルクラス
	 * @return テーブル名
	 */
	public static String getTableName(final Class<?> cls) {
		Table tbl = cls.getAnnotation(Table.class);
		if (tbl != null) {
			return tbl.name();
		}
		return "";
	}

	/**
	 * カラム名取得
	 * @param m メソッド
	 * @return カラム名
	 */
	public static String getColumnName(final Method m) {
		Column col = m.getAnnotation(Column.class);
		if (col != null) {
			return col.name();
		}
		return "";
	}

	/**
	 * Null可確認
	 * @param m メソッド
	 * @return Null可の場合 true を返す。
	 */
	public static boolean isNullable(final Method m) {
		Column col = m.getAnnotation(Column.class);
		return col != null && col.nullable();
	}

	/**
	 * Id確認
	 * @param m メソッド
	 * @return Idの場合 true を返す。
	 */
	public static boolean isId(final Method m) {
		return m.getAnnotation(Id.class) != null;
	}

	/**
	 * バイナリ拡張クラスパス追加
	 * @param cls 追加用クラスパス保持クラス
	 */
	public static void addClassPath(final Class<?> cls) {
		ClassPool pool = ClassPool.getDefault();
		pool.appendClassPath(new ClassClassPath(cls));
	}

	/**
	 * 値取得
	 *
	 * @param obj DAOモデル
	 * @param name 設定項目名
	 * @return 取得値
	 */
	public static Object getValue(final Object obj, final String name) {
		if (obj != null) {
			return Factory.invoke(obj, Factory.getMethod(obj.getClass(), "get" + name));
		}
		return null;
	}

	/**
	 * オブジェクトが指定アイテム名のセッター、ゲッターを持っているか
	 *
	 * @param cls DAOモデルクラス
	 * @param name 指定アイテム
	 * @return 持っている場合 true を返す。
	 */
	public static boolean hasItem(final Class<?> cls, final String name) {
		for (final String item : getModelNames(cls)) {
			if (item.equals(name)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * DAOモデルクラスからDB項目名を配列で返却する。
	 *
	 * @param cls DAOモデルクラス
	 * @return DB項目名配列
	 */
	public static String[] getDBNames(final Class<?> cls) {
		List<String> ret = new ArrayList<>();
		for (final Method mt : cls.getMethods()) {
			if (Factory.isGetter(mt) && !isId(mt)) {
				ret.add(toDBName(Factory.toItemName(mt)));
			}
		}
		return ret.toArray(new String[ret.size()]);
	}

	/**
	 * DAOモデルクラスから項目名を配列で返却する。
	 *
	 * @param cls DAOモデルクラス
	 * @return 項目名配列
	 */
	public static String[] getModelNames(final Class<?> cls) {
		List<String> ret = new ArrayList<>();
		for (final Method mt : cls.getMethods()) {
			if (Factory.isGetter(mt) && !isId(mt)) {
				ret.add(Factory.toItemName(mt));
			}
		}
		return ret.toArray(new String[ret.size()]);
	}

	/**
	 * モデル項目名取得
	 *
	 * @param cols DBカラム名配列
	 * @return モデル項目名配列
	 */
	public static String[] toDBNames(final String... cols) {
		String[] ret = new String[0];
		if (cols != null) {
			ret = new String[cols.length];
			for (int i = 0; i < cols.length; i++) {
				ret[i] = toDBName(cols[i]);
			}
		}
		return ret;
	}

	/**
	 * DB名称取得
	 *
	 * @param name 項目名称
	 * @return DB名称
	 */
	public static String toDBName(final String name) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; name != null && i < name.length(); i = name.offsetByCodePoints(i, 1)) {
			int c = name.codePointAt(i);
			if ('A' <= c && c <= 'Z') {
				if (0 < sb.length()) {
					sb.append('_');
				}
			} else if ('a' <= c && c <= 'z') {
				sb.appendCodePoint(c - ('a' - 'A'));
				continue;
			} else if ('0' <= c && c <= '9') {
				if (0 < i) {
					int pre = name.codePointBefore(i);
					if (pre < '0' || '9' < pre) {
						sb.append('_');
					}
				}
			}
			sb.appendCodePoint(c);
		}
		return sb.toString();
	}
}
